
#include "Parser.h"

Parser::Parser(XmlGrammar & grammar) {
	setLine(1);
	setColumn(0);
	setLevel(0);
	setXmlGrammar(grammar);
}

Parser::~Parser(void) {
}

Status Parser::getStatus() {
	return status;
}

void Parser::setStatus(Status status) {
	if (status == START_READING) {
		// Si se dispone a iniciar la lectura, resetea los contadores y actualiza el estado previo.
		setPreviousStatus( START_READING);
	}
	this->status = status;
}

Status Parser::getPreviousStatus() {
	return previousStatus;
}

void Parser::setPreviousStatus(Status previousStatus) {
	this->previousStatus = previousStatus;
}

list<XMLElement> & Parser::getNodes(void) {
	return nodes;
}

void Parser::setNodes(list<XMLElement> &nodes) {
	this->nodes = nodes;
}
int Parser::getLine(void) {
	return line;
}
void Parser::setLine(int line) {
	this->line = line;
}
int Parser::getColumn(void) {
	return column;
}
void Parser::setColumn(int column) {
	this->column = column;
}
int Parser::getLevel(void) {
	return level;
}
void Parser::setLevel(int level) {
	this->level = level;
}

XmlGrammar Parser::getXmlGrammar(void) {
	return xmlGrammar;
}
void Parser::setXmlGrammar(XmlGrammar & xmlGrammar) {
	this->xmlGrammar = xmlGrammar;
}

XMLElement * Parser::ParseFile(string fileName) {
	ifstream inputStream;
	inputStream.open(fileName.data()); // Asocia el flujo de entrada al archivo a ser leido.

	setStatus( START_READING);
	accumulator.clear(); // Limpia el acumulador.
	try {
		while (inputStream.good()) // Opera con el flujo de entrada mientras sea posible.
		{
			char charBuff = (char) inputStream.get();
			if (charBuff != EOF) {

				this->updateCurrentstatus(charBuff);
				if (getStatus() == READING_TAG || getStatus() == READING_TAG_CLOSING || getStatus() == READING_VALUE
					|| getStatus() == READING_ATTRIBUTE || getStatus() == READING_SIMPLE_TAG_VALUE  
					|| getStatus() == READING_EXTENDED_TAG_VALUE) {
					accumulator.append(1, charBuff); // Almacena los caracteres relevantes en el acumulador.
				}
				if (getStatus() != getPreviousStatus()) {
					switch (getStatus()) {
					case END_TAG_READING:
						this->push_front(XMLElement(accumulator));
						break;
					case END_ATTRIBUTE_READING:
						this->push_front(XmlAttribute(accumulator));
						break;
					case END_VALUE:
						getNodes().front().getAttributes().front().setValue(accumulator);
						accumulator.clear();
						break;
					case CLOSING_SIMPLE_TAG:
						getNodes().front().setValue(accumulator);
						accumulator.clear();
						break;
					case ATTEMPT_TO_READ_TAG_CONTENT:
						if (accumulator.compare("") != 0) {
							this->push_front(XMLElement(accumulator));
						}
						break;
					case ERROR:
						this->informInvalidCharacter(charBuff);
					case TAG_CLOSED: // El cierre de un tag implica desapilar el nodo e incorporarlo a los hijos del anterior en la pila.
						this->pop_front();
						break;
					}
				}
				this->updateElementPosition(charBuff);
			}
		}
		if(getNodes().size() != 1) {
			throw XmlSintaxException("El documento debe tener exactamente una raiz.");
		}
		XMLElement * ptr = new XMLElement();
		*ptr = getNodes().front();
		return ptr;
	}catch(XmlSintaxException e1) {
		throw e1;
	}catch(...) {
		throw XmlSintaxException("Error de sintaxis.");
	}
}

void Parser::updateCurrentstatus(char charBuff) {
	setPreviousStatus(getStatus()); // Actualiza el estado previo.
	map<Status,map<char,Status>> GrammarMap = this->getXmlGrammar().getGrammarMap();
	map<char, Status> StatusMap = GrammarMap[getStatus()];
	char indexValue = charBuff;

	if (this->getXmlGrammar().getGrammarSymbols().find(charBuff) == string::npos) {
		indexValue = isalnum(indexValue) ? 'c' : 's'; // Si es un caracter alphanumeric, el indice es c (common), en otro caso es s (special).
	}
	setStatus(StatusMap[indexValue]);
}

void Parser::updateElementPosition(char charBuff) {
	int columnInc = charBuff == '\t' ? 4 : 1;

	if (charBuff == '\n' || charBuff == '\r') {
		setLine(getLine() + 1);
		setColumn(0);
	} else {
		setColumn(getColumn() + columnInc); // La columna se incrementa por cada caracter leido. Si hay cambio de linea se resetea en el update Status.
	}
}

void Parser::push_front(XMLElement & Node) {
	getNodes().push_front(Node);
	this->setLevel(this->getLevel() + 1);
	getNodes().front().setLevel(this->getLevel());
	getNodes().front().setLine(this->getLine());
	getNodes().front().setColumn(this->getColumn() + 1 - accumulator.size());
	accumulator.clear();
}

void Parser::pop_front(void) {
	XMLElement lastNode = getNodes().front();
	if ((*lastNode.getName()).compare(accumulator) != 0 && accumulator.compare("") != 0) {
		string sintaxErrorCause = "Cierre de tag invalido. Se esperaba cierre de ";
		sintaxErrorCause.append(lastNode.getName()->c_str());
		throw XmlSintaxException(sintaxErrorCause,getLine(),getColumn());
	}
	if (lastNode.getLevel() > 1) {
		getNodes().pop_front();
		getNodes().front().getChildren().push_front(lastNode);
	}
	setLevel(getLevel() - 1);
	accumulator.clear();
}

void Parser::push_front(XmlAttribute & Attribute) {
	getNodes().front().getAttributes().push_front(Attribute);
	getNodes().front().getAttributes().front().setLine(this->getLine());
	getNodes().front().getAttributes().front().setColumn(this->getColumn() + 1 - accumulator.size());
	accumulator.clear();
}

void Parser::informInvalidCharacter(char c) {
	string sintaxErrorCause = "Caracter invalido: ";
	sintaxErrorCause.append(1,c);
	sintaxErrorCause.append(1,'.');
	throw XmlSintaxException(sintaxErrorCause,getLine(),getColumn() + 1);
}
